﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Security.Principal;

namespace InfoPathHelpers.UserRoles
{
    public class UserRoleCollection
    {
        private List<UserRole> _UserRoles;

        public UserRoleCollection(List<UserRole> userRoles)
        {
            _UserRoles = userRoles;
        }

        public bool HasInititatorRole
        {
            get { return GetInititatorRole() != null; }
        }

        public ReadOnlyCollection<UserRole> Roles
        {
            get { return new ReadOnlyCollection<UserRole>(_UserRoles); }
        }

        public UserRole this[string roleName]
        {
            get
            {
                var UserRole = _UserRoles.Find((UserRole role) => { return role.Name == roleName; });
                if(UserRole == null)
                {
                    throw new ArgumentException($"The UserRole \"{roleName}\" does not exist.");
                }
                return UserRole;
            }
        }

        public UserRole GetCurrentRole()
        {
            // InfoPath determines user roles in 3 different ways (in order):
            // 1. The user's name is a value of a field in the form template.
            // 2. The user's name is in Active Directory.
            // 3. The user is a member of an Active Directory group.
            //            
            // We are only interested in 2 and 3. 

            UserRole CurrentRole = null;

            // User names in InfoPath consist of "Domain\UserName". 
            // The user names are not checked against AD so "Machine\UserName" also works.
            // Environment.UserDomainName will be the machine name for non-domain machines.
            var UserName = $"{Environment.UserDomainName}\\{Environment.UserName}";

            // Return the first role which matches the user's name (if any).
            foreach (var UserRole in _UserRoles)
            {
                foreach (var UserAssignment in UserRole.UserAssignments)
                {
                    if (UserAssignment.Type == UserAssignmentType.ActiveDirectoryUser
                        && UserAssignment.Name.Equals(UserName, StringComparison.OrdinalIgnoreCase))
                    {
                        return UserRole;
                    }
                }
            }


            // If no roles matched the user name we need to try and match AD groups.
            // We do this only for domain joined machines with.
            var NetJoinInfo = Win32.NetApi32.GetNetAPIJoinInfo();

            if (NetJoinInfo.IsDomainJoined && NetJoinInfo.NetworkName == Environment.UserDomainName)
            {
                // This gets the list of groups to which the current user belongs.
                // If there is any error it will be empty.
                var GroupMembership = GetCurrentUsersGroupMembership();
                
                foreach (var UserRole in _UserRoles)
                {
                    foreach (var UserAssignment in UserRole.UserAssignments)
                    {

                        if (UserAssignment.Type == UserAssignmentType.ActiveDirectoryGroup &&
                            GroupMembership.Contains(UserAssignment.Name.ToLower()))
                        {
                            return UserRole;
                        }
                    }
                }
            }

            return CurrentRole;
        }

        public UserRole GetDefaultRole()
        {
            return _UserRoles.Find((UserRole role) => {  return role.IsDefault == true; });
        }

        public UserRole GetInititatorRole()
        {
            return _UserRoles.Find((UserRole role) => { return role.IsInitiator == true; });
        }

        public UserRole GetRoleByUserName(string userName)
        {
            var MatchingUserRoles = new List<UserRole>();

            foreach(var UserRole in _UserRoles)
            {
                foreach(var UserAssignment in UserRole.UserAssignments)
                {
                    if(UserAssignment.Name == userName)
                    {
                        return UserRole;
                    }
                }
            }

            return GetDefaultRole();
        }

        private List<string> GetCurrentUsersGroupMembership()
        {
            var GroupMembership = new List<string>();
            
            try
            {
                var DomainName = Environment.UserDomainName;
                var CurrentDomain = Domain.GetCurrentDomain();
                var DirectoryRoot = CurrentDomain.GetDirectoryEntry();

                var Searcher = new DirectorySearcher(DirectoryRoot);
                Searcher.SearchScope = SearchScope.Subtree;
                Searcher.PropertiesToLoad.Add("memberOf");

                Searcher.Filter = string.Format($"(&(objectCategory=person)(samAccountName={Environment.UserName}))");

                var Result = Searcher.FindOne();

                if (Result != null)
                {
                    foreach (string Group in Result.Properties["memberOf"])
                    {
                        var StartIndex = Group.IndexOf("=") + 1;
                        var EndIndex = Group.IndexOf(",", StartIndex);
                        var GroupName = Group.Substring(StartIndex, EndIndex - StartIndex);
                        GroupMembership.Add($"{DomainName}\\{GroupName}".ToLower());
                    }
                }
            }
            catch
            {
                // Exceptions are not handled. An empty list is returned.
            }
            return GroupMembership;
        }
    }
}
